<?php
declare(strict_types=1);

namespace TairClient\Client;

use Hyperf\GrpcServer\Exception\GrpcException;
use Hyperf\Redis\Redis;
use Hyperf\Utils\Codec\Json;
use Hyperfx\Framework\Logger\Logx;

class TairTs extends TairBase {

    public function __construct(protected Redis $client)
    {

    }

    /**
     * 向Skey中插入一条Datapoint数据。若Pkey或Skey不存在则会自动创建，属性（过期时间、是否开启压缩等）仅在Skey不存在并自动创建的情况下生效。
     *
     * @link https://help.aliyun.com/document_detail/443817.html#section-xcc-7ui-7ky
     *
     * EXTS.S.ADD foo temperature 1644310456023 30.5 DATA_ET 1000000 LABELS sensor_id 1
     *
     * @var string $pKey PKey名称（TairTS数据结构），用于指定命令调用的TairTS对象。
     * @var string $sKey 指定多条数据的个数。
     * @var int|string $ts Datapoint数据的Unix时间戳，单位为毫秒，支持用*表示系统当前的时间戳。。
     * @var float $value Datapoint数据的值，数据类型为双精度浮点（double）型。
     * @var int $dateEt DataPoint数据的相对过期时间，单位为毫秒，默认0为不填（表示不会过期）
     * @var int $chunkSize 单个Chunk可保存的Datapoint数，默认值为256条，取值范围为[1,256]。
     * @var array $labels Skey的属性，输入一组或多组对应的标签名、标签值，例如["user_id": 1, "partner_id": 161]
     * @return bool
     */
    public function add(string $pKey, string $sKey, int|string $ts, float $value, int $dateEt, int $chunkSize, array $labels): bool {
        $this->checkNotEmpty($pKey);
        $this->checkNotEmpty($sKey);
        $this->checkNotEmpty($ts);
        $this->checkNotEmpty($chunkSize);

        $args = [$pKey, $sKey, $ts, $value];
        if ($dateEt > 0) {
            $args[] = 'DATA_ET';
            $args[] = $dateEt;
        }
        $args[] = 'CHUNK_SIZE';
        $args[] = $chunkSize;
        $this->praseLables($args, $labels);

        return $this->then(function ($args) {
            return $this->client->rawCommand('EXTS.S.ADD', ...$args);
        }, [$args], function () use ($args) {
            return sprintf('EXTS.S.ADD %s', implode(' ', $args));
        });
    }

    private function praseLables(array &$args, array $labels) {
        if (!empty($labels)) {
            $args[] = 'LABELS';
            foreach ($labels as $key => $vals) {
                if (is_array($vals)) {
                    foreach ($vals as $val) {
                        $args[] = $key;
                        $args[] = $val;
                    };
                    continue;
                }
                $args[] = $key;
                $args[] = $vals;
            }
        }
    }

    /**
     * 在指定Pkey的多个Skey分中别插入一条Datapoint数据。若Pkey或Skey不存在则会自动创建，属性（过期时间、是否开启压缩等）仅在Skey不存在并自动创建的情况下生效。
     *
     * @link https://help.aliyun.com/document_detail/443817.html#section-swv-h4k-dvq
     *
     * EXTS.S.MADD foo 3 temperature * 30.2 pressure * 2.05 distance * 0.5
     *
     * @var string $pKey PKey名称（TairTS数据结构），用于指定命令调用的TairTS对象。
     * @var int $keyNumber 指定多条数据的个数。
     * @var array $sKeyData 多组数据 [[skey, ts, value], [skey1, ts, value]]
     * @var int $dateEt DataPoint数据的相对过期时间，单位为毫秒，默认0为不填（表示不会过期）
     * @var int $chunkSize 单个Chunk可保存的Datapoint数，默认值为256条，取值范围为[1,256]。
     * @var array $labels Skey的属性，输入一组或多组对应的标签名、标签值，例如["user_id": 1, "partner_id": 161]
     * @return array [0=> true, 1 => false]
     */
    public function mAdd(string $pKey, int $keyNumber, array $sKeyData, int $dateEt, int $chunkSize, array $labels): array {
        $this->checkNotEmpty($pKey);
        $this->checkNotEmpty($keyNumber);
        $this->checkNotEmpty($sKeyData);
        $this->checkNotEmpty($chunkSize);

        $args = [$pKey, $keyNumber];
        foreach ($sKeyData as $item) {
            array_push($args, ...$item);
        }
        if ($dateEt > 0) {
            $args[] = 'DATA_ET';
            $args[] = $dateEt;
        }

        $args[] = 'CHUNK_SIZE';
        $args[] = $chunkSize;

        $this->praseLables($args, $labels);

        $ret = $this->then(function ($args) {
            return $this->client->rawCommand('EXTS.S.MADD', ...$args);
        }, [$args], function () use ($args) {
            return sprintf('EXTS.S.MADD %s', implode(' ', $args));
        });

        return $ret;
    }

    /**
     * 修改指定Skey的元数据信息，当前仅支持修改过期时间（DATA_ET）。
     *
     * @link https://help.aliyun.com/document_detail/443817.html#section-kc9-1jj-b6t
     *
     * EXTS.S.ADD foo temperature 1644310456023 30.5 DATA_ET 1000000 LABELS sensor_id 1
     *
     * @var string $pKey PKey名称（TairTS数据结构），用于指定命令调用的TairTS对象。
     * @var string $sKey 指定多条数据的个数。
     * @var int $dateEt DataPoint数据的相对过期时间，单位为毫秒，默认0为不填（表示不会过期）
     * @return bool
     */
    public function alter(string $pKey, string $sKey, int $dateEt): bool {
        $this->checkNotEmpty($pKey);
        $this->checkNotEmpty($sKey);
        $this->checkNotEmpty($dateEt);

        $args = [$pKey, $sKey, 'DATA_ET', $dateEt];
        return $this->then(function ($args) {
            return $this->client->rawCommand('EXTS.S.ALTER', ...$args);
        }, [$args], function () use ($args) {
            return sprintf('EXTS.S.ALTER %s', implode(' ', $args));
        });
    }

    /**
     * 删除指定Pkey中的单个Skey，并删除目标Skey中所有的Datapoint数据。
     *
     * @link https://help.aliyun.com/document_detail/443817.html#section-cc4-ihi-4u4
     *
     * EXTS.S.DEL foo temperature
     *
     * @var string $pKey PKey名称（TairTS数据结构），用于指定命令调用的TairTS对象。
     * @var string $sKey 指定多条数据的个数。
     * @return bool
     */
    public function del(string $pKey, string $sKey): bool {
        $this->checkNotEmpty($pKey);
        $this->checkNotEmpty($sKey);

        $args = [$pKey, $sKey];
        return (bool) $this->then(function ($args) {
            return $this->client->rawCommand('EXTS.S.DEL', ...$args);
        }, [$args], function () use ($args) {
            return sprintf('EXTS.S.DEL %s', implode(' ', $args));
        }, false);
    }

    /**
     * 在Skey中自定义过滤条件（filter）与查询时间点（包含指定时间点），查询目标Datapoint数据。
     *
     * @link https://help.aliyun.com/document_detail/443817.html#section-nbg-zuv-4zq
     *
     * EXTS.S.MRANGE foo 1644451031662 * AGGREGATION MAX 10000 WITHLABELS FILTER sensor_id=1
     *
     * @var string $pKey PKey名称（TairTS数据结构），用于指定命令调用的TairTS对象。
     * @var int $fromTs 指定多条数据的个数。
     * @var int|string $toTs
     * @var string $aggregationType 聚合类型，例如MAX（最大值）、AVG（平均值）、SUM（求和）等，更多信息请参见聚合功能语法。
     * @var int $timeBucket 采样间隔，单位为毫秒，最小值为1,000毫秒
     * @var bool $isWithLables 聚合类型，例如MAX（最大值）、AVG（平均值）、SUM（求和）等，更多信息请参见聚合功能语法。
     * @var array $filter 过滤条件，您可以根据Skey的标签（LABELS）过滤目标Skey，更多信息请参见
     * @return array
     */
    public function mRange(string $pKey, int $fromTs, int|string $toTs, int $maxCount, string $aggregationType, int $timeBucket, bool $isWithLables, array $filters) {
        $this->checkNotEmpty($pKey);
        $this->checkNotEmpty($fromTs);
        $this->checkNotEmpty($toTs);
        $this->checkNotEmpty($aggregationType);
        $this->checkNotEmpty($timeBucket);

        if ($maxCount > 0) {
            $args[] = $maxCount;
        }
        $args = [$pKey, $fromTs, $toTs, 'AGGREGATION', $aggregationType, $timeBucket];
        if ($isWithLables) {
            $args[] = 'WITHLABELS';
        }
        if (!empty($filters)) {
            $args[] = 'FILTER';
            $args[] = sprintf('%s%s%s', $filters[0], $filters[1], $filters[2]);
        }
        return $this->then(function ($args) {
            return $this->client->rawCommand('EXTS.S.MRANGE', ...$args);
        }, [$args], function () use ($args) {
            return sprintf('EXTS.S.MRANGE %s', implode(' ', $args));
        });
    }

    /**
     * 在Skey中自定义过滤条件（filter）与查询时间点（包含指定时间点），查询目标Datapoint数据。
     *
     * @link https://help.aliyun.com/document_detail/443817.html#section-nbg-zuv-4zq
     *
     * EXTS.S.MRANGE foo 1644451031662 * AGGREGATION MAX 10000 WITHLABELS FILTER sensor_id=1
     *
     * @var string $pKey PKey名称（TairTS数据结构），用于指定命令调用的TairTS对象。
     * @var int $fromTs 查询的开始时间（Unix时间戳），单位为毫秒。
     * @var int|string $toTs
     * @var string $pkeyAggregationType Pkey的聚合类型，更多信息请参见聚合功能语法。
     * @var string $pkeyTimeBucket Pkey的采样间隔，单位为毫秒，最小值为1,000毫秒。
     * @var string $aggregationType 聚合类型，例如MAX（最大值）、AVG（平均值）、SUM（求和）等，更多信息请参见聚合功能语法。
     * @var int $timeBucket 采样间隔，单位为毫秒，最小值为1,000毫秒
     * @var bool $isWithLables 聚合类型，例如MAX（最大值）、AVG（平均值）、SUM（求和）等，更多信息请参见聚合功能语法。
     * @var array $filter 过滤条件，您可以根据Skey的标签（LABELS）过滤目标Skey，更多信息请参见
     * @return bool|array
     */
    public function pRange(string $pKey, int $fromTs, int|string $toTs, int $maxCount, string $pkeyAggregationType, int $pkeyTimeBucket, string $aggregationType, int $timeBucket, bool $isWithLables, array $filters): array|bool {
        $this->checkNotEmpty($pKey);
        // $this->checkNotEmpty($fromTs);
        $this->checkNotEmpty($toTs);
        $this->checkNotEmpty($pkeyAggregationType);
        $this->checkNotEmpty($pkeyTimeBucket);

        if ($maxCount > 0) {
            $args[] = $maxCount;
        }
        $args = [$pKey, $fromTs, $toTs, $pkeyAggregationType, $pkeyTimeBucket];
        if (!empty($aggregationType)) {
            $args[] = 'AGGREGATION';
            $args[] = $aggregationType;
            $args[] =  $timeBucket;
        }
        if ($isWithLables) {
            $args[] = 'WITHLABELS';
        }
        if (!empty($filters)) {
            $args[] = 'FILTER';
            $args[] = sprintf('%s%s%s', $filters[0], $filters[1], $filters[2]);
        }
        return $this->then(function ($args) {
            return $this->client->rawCommand('EXTS.P.RANGE', ...$args);
        }, [$args], function () use ($args) {
            return sprintf('EXTS.P.RANGE %s', implode(' ', $args));
        }, false);
    }
}